﻿using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using Standard;

namespace TestChromeWindow;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }


    [SecurityCritical]
    [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS pMarInset);


    [StructLayout(LayoutKind.Sequential)]
    public struct MARGINS
    {
        /// <summary>Width of left border that retains its size.</summary>
        public int cxLeftWidth;

        /// <summary>Width of right border that retains its size.</summary>
        public int cxRightWidth;

        /// <summary>Height of top border that retains its size.</summary>
        public int cyTopHeight;

        /// <summary>Height of bottom border that retains its size.</summary>
        public int cyBottomHeight;
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        var hwnd = new WindowInteropHelper(this).Handle;
        HwndSource.FromHwnd(hwnd)?.AddHook(Hook);
    }

    private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        const int WM_ACTIVATE = 0x0006;
        const int NCCALCSIZE = 0x0083;
        const int NCHITTEST = 0x0084;
        if (msg == WM_ACTIVATE)
        {
            var dpi = VisualTreeHelper.GetDpi(this);

            var deviceGlassThickness = DpiHelper.LogicalThicknessToDevice(new Thickness(10), dpi.DpiScaleX, dpi.DpiScaleY);

            var dwmMargin = new MARGINS
            {
                // err on the side of pushing in glass an extra pixel.
                cxLeftWidth = (int)Math.Ceiling(deviceGlassThickness.Left),
                cxRightWidth = (int)Math.Ceiling(deviceGlassThickness.Right),
                cyTopHeight = (int)Math.Ceiling(deviceGlassThickness.Top),
                cyBottomHeight = (int)Math.Ceiling(deviceGlassThickness.Bottom),
            };

            DwmExtendFrameIntoClientArea(hwnd, ref dwmMargin);
        }

        if (msg == NCCALCSIZE)
        {
            handled = true;

            // Per MSDN for NCCALCSIZE, always return 0 when wParam == FALSE
            // 
            // Returning 0 when wParam == TRUE is not appropriate - it will preserve
            // the old client area and align it with the upper-left corner of the new 
            // client area. So we simply ask for a redraw (WVR_REDRAW)

            IntPtr retVal = IntPtr.Zero;
            if (wparam.ToInt32() != 0) // wParam == TRUE
            {
                retVal = new IntPtr(768);
            }

            return retVal;
        }

        if (msg == NCHITTEST)
        {
            var dpi = this.GetDpi();

            // Let the system know if we consider the mouse to be in our effective non-client area.
            var mousePosScreen = new Point(Utility.GET_X_LPARAM(lparam), Utility.GET_Y_LPARAM(lparam));
            Rect windowPosition = _GetWindowRect(hwnd); // todo: 如果出问题，那就在外边缓存窗口句柄，这里就不用参数里的hwnd了

            Point mousePosWindow = mousePosScreen;
            mousePosWindow.Offset(-windowPosition.X, -windowPosition.Y);
            mousePosWindow = DpiHelper.DevicePixelsToLogical(mousePosWindow, dpi.DpiScaleX, dpi.DpiScaleY);

            // If the app is asking for content to be treated as client then that takes precedence over _everything_, even DWM caption buttons.
            // This allows apps to set the glass frame to be non-empty, still cover it with WPF content to hide all the glass,
            // yet still get DWM to draw a drop shadow.
            // IInputElement inputElement = this.InputHitTest(mousePosWindow);
            // if (inputElement != null)
            // {
            //     return new IntPtr((int)HT.CLIENT);
            //     
            //     // if (WindowChromeFix.GetIsHitTestVisibleInChrome(inputElement))
            //     // {
            //     //     handled = true;
            //     //     return new IntPtr((int)HT.CLIENT);
            //     // }
            // }
            

            HT ht = _HitTestNca(
                DpiHelper.DeviceRectToLogical(windowPosition, dpi.DpiScaleX, dpi.DpiScaleY),
                DpiHelper.DevicePixelsToLogical(mousePosScreen, dpi.DpiScaleX, dpi.DpiScaleY));

            handled = true;
            return new IntPtr((int)ht);
        }

        return IntPtr.Zero;
    }


    /// <summary>
    /// Get the bounding rectangle for the window in physical coordinates.
    /// </summary>
    /// <returns>The bounding rectangle for the window.</returns>
    private Rect _GetWindowRect(IntPtr hwnd)
    {
        // Get the window rectangle.
        RECT windowPosition = NativeMethods.GetWindowRect(hwnd);
        return new Rect(windowPosition.Left, windowPosition.Top, windowPosition.Width, windowPosition.Height);
    }

    /// <summary>
    /// Matrix of the HT values to return when responding to NC window messages.
    /// </summary>
    [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Member")]
    private static readonly HT[,] _HitTestBorders = new[,]
    {
        { HT.TOPLEFT, HT.TOP, HT.TOPRIGHT },
        { HT.LEFT, HT.CLIENT, HT.RIGHT },
        { HT.BOTTOMLEFT, HT.BOTTOM, HT.BOTTOMRIGHT },
    };
    
    private ChromeFixInfo ChromeFixInfo = new();

    private HT _HitTestNca(Rect windowPosition, Point mousePosition)
    {
        // Determine if hit test is for resizing, default middle (1,1).
        int uRow = 1;
        int uCol = 1;
        bool onResizeBorder = false;

        // Determine if the point is at the top or bottom of the window.
        if (mousePosition.Y >= windowPosition.Top && mousePosition.Y < windowPosition.Top + ChromeFixInfo.ResizeBorderThickness.Top + ChromeFixInfo.CaptionHeight)
        {
            onResizeBorder = (mousePosition.Y < (windowPosition.Top + ChromeFixInfo.ResizeBorderThickness.Top));
            uRow = 0; // top (caption or resize border)
        }
        else if (mousePosition.Y < windowPosition.Bottom && mousePosition.Y >= windowPosition.Bottom - (int)ChromeFixInfo.ResizeBorderThickness.Bottom)
        {
            uRow = 2; // bottom
        }

        // Determine if the point is at the left or right of the window.
        if (mousePosition.X >= windowPosition.Left && mousePosition.X < windowPosition.Left + (int)ChromeFixInfo.ResizeBorderThickness.Left)
        {
            uCol = 0; // left side
        }
        else if (mousePosition.X < windowPosition.Right && mousePosition.X >= windowPosition.Right - ChromeFixInfo.ResizeBorderThickness.Right)
        {
            uCol = 2; // right side
        }

        // If the cursor is in one of the top edges by the caption bar, but below the top resize border,
        // then resize left-right rather than diagonally.
        if (uRow == 0 && uCol != 1 && !onResizeBorder)
        {
            uRow = 1;
        }

        HT ht = _HitTestBorders[uRow, uCol];

        if (ht == HT.TOP && !onResizeBorder)
        {
            ht = HT.CAPTION;
        }

        return ht;
    }
}